﻿using System;
using System.Configuration;
using System.IO;
using System.Web;
using System.Web.Caching;
using NLog;

namespace Softio.Mscd.Client
{
    public class StaticFile
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        private static object locker = new object();
        private static string _mscdSiteAddress;
        private static string _mscdResourcesDirectory;
        private static bool _specifiedSiteAddress;
        private static bool _specifiedResourcesDirectory = false;
        private static bool _mscdSiteSecuredChannel = false;

        /// <summary>
        /// List of content types.
        /// </summary>
        public enum ContentTypes
        {
            /// <summary>
            /// Stylesheet
            /// </summary>
            Stylesheet,
            /// <summary>
            /// JavaScript
            /// </summary>
            JavaScript,
            /// <summary>
            /// Other
            /// </summary>
            Other
        }

        static StaticFile()
        {
            _mscdResourcesDirectory = ConfigurationManager.AppSettings["MSCD.ResourcesDirectory"];
            if (string.IsNullOrEmpty(_mscdResourcesDirectory))
            {
                throw new Exception("'MSCD.ResourcesDirectory' key not found in web.config file. Provide full qualified path to the resources on disc (eg. c:\\StaticFiles).");
            }
            logger.Trace("'MSCD.ResourcesDirectory' configured as: {0}", _mscdResourcesDirectory);

            _mscdSiteAddress = ConfigurationManager.AppSettings["MSCD.StaticSiteAddress"];
            if (string.IsNullOrEmpty(_mscdSiteAddress))
            {
                throw new Exception("'MSCD.StaticSiteAddress' key not found in web.config file. Provide full qualified address to the resources site (eg. http://static.mysite.com).");
            }
            if (!_mscdSiteAddress.EndsWith("/")) { _mscdSiteAddress += "/"; }
            logger.Trace("'MSCD.StaticSiteAddress' configured as: {0}", _mscdSiteAddress);

            _specifiedSiteAddress = !_mscdSiteAddress.StartsWith("~");

            bool secChannel;
            if (!string.IsNullOrEmpty(ConfigurationManager.AppSettings["MSCD.StaticSiteSecuredChannel"]) &&
                Boolean.TryParse(ConfigurationManager.AppSettings["MSCD.StaticSiteSecuredChannel"], out secChannel))
            {
                _mscdSiteSecuredChannel = secChannel;
                logger.Trace("'MSCD.StaticSiteSecuredChannel' configured as: {0}", secChannel.ToString().ToUpperInvariant());
            }
            else
            {
                logger.Trace("'MSCD.StaticSiteSecuredChannel' configured as default: false");
            }
        }

        public static string StylesPackage(string name, string version)
        {
            return GetSrc(name, ContentTypes.Stylesheet, version);
        }

        public static string StylesPackage(string name)
        {
            return GetSrc(name, ContentTypes.Stylesheet, null);
        }

        public static string ScriptPackage(string name, string version)
        {
            return GetSrc(name, ContentTypes.JavaScript, version);
        }

        public static string ScriptPackage(string name)
        {
            return GetSrc(name, ContentTypes.JavaScript, null);
        }

        public static string Href(string src, string version)
        {
            return GetSrc(src, ContentTypes.Other, version);
        }

        public static string Href(string src)
        {
            return GetSrc(src, ContentTypes.Other, null);
        }

        public static string GetPath(string dir)
        {
            return HrefDry(dir);
        }

        public static string HrefDry(string src)
        {
            if (_specifiedSiteAddress)
            {
                return String
                    .Concat(_mscdSiteAddress, src.StartsWith("/") ? src.Remove(0, 1) : src)
                    .ToLowerInvariant();
            }

            string url = GetRequestUrl();
            return String
                .Concat(_mscdSiteAddress.Replace("~", url), src.StartsWith("/") ? src.Remove(0, 1) : src)
                .ToLowerInvariant();
        }

        private static string GetRequestUrl()
        {
            Uri u = HttpContext.Current.Request.Url;
            if (u.Port == 80 || u.Port == 443)
            {
                if (u.Port == 443 && !_mscdSiteSecuredChannel)
                {
                    return String.Format("http://{0}", u.Host);
                }
                return String.Format("{0}://{1}", u.Scheme, u.Host);
            }
            else
            {
                return String.Format("{0}://{1}:{2}", u.Scheme, u.Host, u.Port);
            }
        }

        private static string GetSrc(string src, ContentTypes type, string version)
        {
            HttpContext ctx = HttpContext.Current;
            string key = String.Concat(ctx.Request.Url.Host, ctx.Request.Url.Port, src, type.ToString(), version ?? "");
            string cache = ctx.Cache[key] as string;
            if (cache == null)
            {
                string hash, resource = "";
                switch (type)
                {
                    case ContentTypes.Other:
                        if (version == null)
                        {
                            hash = CalculatePhysicalFileHash(src, out resource);
                        }
                        else
                        {
                            hash = version;
                        }
                        src = HrefDry(Path.ChangeExtension(src, String.Concat("_v", hash, Path.GetExtension(src))).Replace("._v", "_v"));
                        break;
                    case ContentTypes.JavaScript:
                        if (version == null)
                        {
                            hash = CalculatePhysicalDirectoryHash("scripts/" + src, "*.js", out resource);
                        }
                        else
                        {
                            hash = version;
                        }
                        src = HrefDry(String.Concat("scripts/", src, "_v", hash, ".js"));
                        break;
                    case ContentTypes.Stylesheet:
                        if (version == null)
                        {
                            hash = CalculatePhysicalDirectoryHash("styles/" + src, "*.css", out resource);
                        }
                        else
                        {
                            hash = version;
                        }
                        src = HrefDry(String.Concat("styles/", src, "_v", hash, ".css"));
                        break;
                }
                if (resource.Length > 0)
                {
                    ctx.Cache.Insert(
                        key,
                        src,
                        new CacheDependency(resource),
                        DateTime.Now.AddDays(30),
                        Cache.NoSlidingExpiration);
                }
                return src;
            }
            return cache;
        }

        private static string CalculatePhysicalFileHash(string src, out string resource)
        {
            src = src.StartsWith("/") ? src.Remove(0, 1) : src;
            resource = Path.Combine(GetResourcesDirectory(), src);
            try
            {
                if (File.Exists(resource))
                {
                    FileInfo fi = new FileInfo(resource);
                    return fi.LastWriteTimeUtc.GetHashCode().ToString("X").ToLowerInvariant();
                }
            }
            catch (Exception e)
            {
                logger.Error(e.Message);
            }
            return "";
        }

        private static string CalculatePhysicalDirectoryHash(string key, string searchPattern, out string resource)
        {
            resource = Path.Combine(GetResourcesDirectory(), key);
            try
            {
                if (Directory.Exists(resource))
                {
                    DirectoryInfo di = new DirectoryInfo(resource);
                    FileSystemInfo[] files = di.GetFileSystemInfos(searchPattern);
                    int hash = 0;
                    foreach (FileSystemInfo fi in files)
                    {
                        hash += fi.LastWriteTimeUtc.GetHashCode() / files.Length;
                    }
                    return hash.ToString("X").ToLowerInvariant();
                }
            }
            catch (Exception e)
            {
                logger.Error(e.Message);
            }
            return "";
        }

        private static string GetResourcesDirectory()
        {
            if (!_specifiedResourcesDirectory)
            {
                lock (locker)
                {
                    if (!_specifiedResourcesDirectory)
                    {
                        if (_mscdResourcesDirectory.StartsWith("~/"))
                        {
                            HttpContext ctx = HttpContext.Current;
                            _mscdResourcesDirectory = ctx.Server.MapPath(_mscdResourcesDirectory);
                        }
                        _specifiedResourcesDirectory = true;
                    }
                }
            }
            return _mscdResourcesDirectory;
        }
    }
}